package fitness;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Random;

import utils.MyUtil;


public class ODESineFitness implements Serializable {

	/**
	 * 
	 */
	private static final long serialVersionUID = 1L;
	
	protected static String outputName = "X"; //Which species is considered the output
	
	protected static int skipTime = 300;

	protected static double penalty = 10000;
	
	protected static int numberofperiods = 10;
	
	public static double goal(int i, int firstPeak, double period, double height) {
		return (1 + Math.sin((i - firstPeak) * 2 * Math.PI
				/ period + Math.PI / 2))
				* height / 2;
	}

	public static OscillatorBasedResult evaluateFitness(double[][] data,ArrayList<String> symbols) {
		OscillatorBasedResult result = new OscillatorBasedResult();
		
		result.mse = Double.MAX_VALUE;
		result.count = 0;
		result.limitcycle = .1;
		if (symbols.size() == 1) {
			result.sde = new Random().nextDouble() / penalty;
			result.mse = 1000000;
			return result;
		}
		
		try {
			double period = 0;
			int output = symbols.indexOf(outputName);
			MyUtil.windowSizeForPeakSearching = 4;
			int firstPeak = MyUtil.getFirstPeak(data[output],
					skipTime);
			//System.out.println(firstPeak);
			if (firstPeak == -1
					|| firstPeak + 10 > data[output].length) {
				int firstPeak2 = MyUtil.getFirstPeak(data[output], 0);
				if (firstPeak2 == -1) {
					result.sde = new Random().nextDouble()
							/ penalty;
					result.mse = 1000000;
					return result;
				} else {
					result.sde = (double) 1 / penalty;
					result.mse = 100000;
					return result;
				}
			} else {
				int nextPeak = MyUtil.getFirstPeak(data[output], firstPeak
						+ 10);
				//System.out.println(nextPeak);
				if (nextPeak != -1) {
					period = nextPeak - firstPeak;
					result.average = period; // dirty hack;
					result.limitcycle = 1
							/ (1 + Math.abs(data[output][firstPeak]
									- data[output][nextPeak])
									/ data[output][firstPeak])
							/ (1 + Math.abs(data[output][firstPeak]
									- data[output][nextPeak])
									/ data[output][firstPeak]); // changed YR
				}
			}

			double mse = 0;
			double average = 0;
			double averageGoal = 0;

			int calculationCount = 0;
			for (int i = Math.max(firstPeak, skipTime); i < Math
					.min(firstPeak + period
							* (numberofperiods + 1),
							data[output].length - 1); i++) {
				average += data[output][i];
				averageGoal += goal(i, firstPeak, period, data[output][firstPeak]);
				calculationCount++;
			}

			average = average / calculationCount;
			averageGoal = averageGoal / calculationCount;
			double standdev = 0;
			double standdevGoal = 0;
			int count = 0;

			for (int i = Math.max(firstPeak, skipTime); i < Math
					.min(firstPeak + period
							* (numberofperiods + 1),
							data[output].length - 1); i++) {
				double goal = goal(i, firstPeak, period, data[output][firstPeak]);
				double error = goal - data[output][i];
				mse += error * error;
				standdev += (average - data[output][i])
						* (average - data[output][i]);
				standdevGoal += (averageGoal - goal) * (averageGoal - goal);
				count++;
			}
			standdev /= count;
			result.mse = mse / count;
			result.sde = Math.min(5, standdev);
			if (firstPeak == -1) {
				result.sde /= penalty;
			}

		} catch (Exception ex) {
			ex.printStackTrace();
		}
		
		return result;
	}

	public FitnessResult minFitness() {
		OscillatorBasedResult result = new OscillatorBasedResult();
		result.mse = Double.MAX_VALUE;
		result.count = 0;
		result.limitcycle = .1;
		return result;
	}


}
